import { RecorderAnalyser } from '../Analyser/RecorderAnalyser';
import { AudioAnalyser } from '../Analyser/AudioAnalyser';
import { AnalyserOptions } from '../Analyser/OriginAnalyser';
import { drawWave, drawStrip } from './drawFunctions';

export enum DrawType {
  wave = 'wave',
  strip = 'strip',
  custom = 'custom'
}

const DEFAULT_WAVE_OPTIONS = {
  color: '#187eff',
  lineWidth: 1
};

const DEFAULT_STRIP_OPTIONS = {
  color: '#187eff',
  lineWidth: 1,
  heightDivisor: 4,
  gap: 1
};

export type WaveOptions = Partial<typeof DEFAULT_WAVE_OPTIONS>;
export type StripOptions = Partial<typeof DEFAULT_STRIP_OPTIONS>;

export type DrawConfigs =
  | {
      type: DrawType.wave;
      config: WaveOptions;
    }
  | {
      type: DrawType.strip;
      config: StripOptions;
    }
  | {
      type: DrawType.custom;
      drawFun: (ctx: CanvasRenderingContext2D, data: Uint8Array) => void;
    };

const DEFAULT_OPTIONS = {};

export type CanvasOptions = Partial<typeof DEFAULT_OPTIONS> & {
  audio?: AnalyserOptions;
  fps?: number;
} & { draw?: DrawConfigs };

export class AudioCanvas {
  private _isPlaying = false;
  private _context: CanvasRenderingContext2D;
  // private _canvasOptions: CanvasOptions;
  private _drawType: DrawConfigs;
  private _audioAnalyser: AudioAnalyser | null = null;
  private _recorderAnalyser: RecorderAnalyser | null = null;
  private _gapTime: number;
  constructor(
    private _canvasElement: HTMLCanvasElement,
    audio: string | HTMLAudioElement | 'recorder',
    canvasOptions: CanvasOptions = DEFAULT_OPTIONS
  ) {
    if (audio === 'recorder') {
      this._recorderAnalyser = new RecorderAnalyser(canvasOptions.audio);
    } else {
      this._audioAnalyser = new AudioAnalyser(audio, canvasOptions.audio);
    }

    this._gapTime = 1000 / (canvasOptions.fps || 30);
    // this._canvasOptions = {
    //   ...DEFAULT_OPTIONS,
    //   ...canvasOptions
    // };
    this._drawType = canvasOptions.draw || {
      type: DrawType.wave,
      config: DEFAULT_WAVE_OPTIONS
    };
    const context = this._canvasElement.getContext('2d');
    if (context) {
      this._context = context;
    } else {
      throw new Error('no support canvas 2d');
    }
    this._addAudioEvent();
  }

  private _playAudio() {
    this._isPlaying = true;
    this._draw(performance.now());
  }

  private _pauseAudio() {
    this._isPlaying = false;
  }

  private _addAudioEvent() {
    if (this._audioAnalyser) {
      this._audioAnalyser.audioElement.addEventListener('play', () => this._playAudio());
      this._audioAnalyser.audioElement.addEventListener('pause', () => this._pauseAudio());
      this._audioAnalyser.audioElement.addEventListener('ended', () => this._pauseAudio());
    }
  }

  private _getData() {
    if (this._audioAnalyser) {
      return this._audioAnalyser.getData();
    }
    if (this._recorderAnalyser) {
      return this._recorderAnalyser.getData();
    }
    return new Uint8Array();
  }

  private _drawStrip(config: StripOptions) {
    const { height, width } = this._canvasElement;
    drawStrip(this._context, this._getData(), {
      ...DEFAULT_STRIP_OPTIONS,
      ...config,
      height,
      width
    });
  }
  private _drawWave(config: WaveOptions) {
    const { height, width } = this._canvasElement;
    drawWave(this._context, this._getData(), {
      ...DEFAULT_WAVE_OPTIONS,
      ...config,
      height,
      width
    });
  }

  private _draw(last: number) {
    if (!this._isPlaying) return;
    const now = performance.now();
    if (now - last > this._gapTime) {
      switch (this._drawType.type) {
        case DrawType.custom:
          this._drawType.drawFun(this._context, this._getData());
          break;
        case DrawType.strip:
          this._drawStrip(this._drawType.config);
          break;
        case DrawType.wave:
          this._drawWave(this._drawType.config);
          break;
        default:
          break;
      }
      requestAnimationFrame(() => this._draw(now));
    } else {
      requestAnimationFrame(() => this._draw(last));
    }
  }

  clear() {
    this._context.clearRect(0, 0, this._canvasElement.width, this._canvasElement.height);
  }

  play() {
    if (this._audioAnalyser) {
      this._audioAnalyser.audioElement.play();
    }
    if (this._recorderAnalyser) {
      this._recorderAnalyser.start();
      this._playAudio();
    }
  }

  pause() {
    if (this._audioAnalyser) {
      this._audioAnalyser.audioElement.pause();
    }
    if (this._recorderAnalyser) {
      this._recorderAnalyser.stop();
      this._pauseAudio();
    }
  }
}
